#include "DisplayWidget.h"
#include "ui_DisplayWidget.h"

#include <QPainter>
#include <QDebug>

#include "ImageUtility.h"

#include <qmath.h>

#define DEG2RAD(degrees) degrees * M_PI / 180.0

DisplayWidget::DisplayWidget(QWidget *parent) :
    QGLWidget(parent),
    ui(new Ui::DisplayWidget),
    mask(NULL)
{

    ui->setupUi(this);

    masks.append(new Mask(QImage(":/resources/images/vader.png")));
    masks.first()->setFace(Face(QRect(78, 137, 190, 180)));
    masks.append(new Mask(QImage(":/resources/images/potter.png")));
    masks.append(new Mask(QImage(":/resources/images/jimmer.png")));
    masks.append(new Mask(QImage(":/resources/images/barrett.png")));
    masks.append(new Mask(QImage(":/resources/images/obama.png")));
    masks.append(new Mask(QImage(":/resources/images/hillary.png")));

    this->switchMasks();

    maskTimer.setInterval(15000);
    connect(&maskTimer, SIGNAL(timeout()), this, SLOT(switchMasks()));
    maskTimer.start();
}

DisplayWidget::~DisplayWidget()
{
    delete ui;
}

void DisplayWidget::switchMasks()
{
    static int maskNumber = 0;
    maskNumber = (maskNumber + 1) % masks.count();

    mask = masks[maskNumber];
}

void DisplayWidget::setRects(QVector<QRect> rects)
{
    currentRects = rects;
    this->repaint();
}

void DisplayWidget::setFaces(QVector<Face> faces)
{
    currentFaces = faces;

    this->repaint();
}

void DisplayWidget::newImage(QImage image)
{
    currentImage = image;

    static bool firstTime = true;
    if (firstTime) {
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glOrtho(0, currentImage.width(), currentImage.height(), 0, 0, 1);

        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        this->createNewFrameTexture();
        firstTime = false;
    }
//currentImage = ImageUtility::convertToGrayscale(currentImage);
    QPainter painter(&currentImage);

    QVector<Face> faces;
    if (currentFaces.count() > 0) {
        faces = currentFaces;
        previousFaces = currentFaces;
    } else {
        faces = previousFaces;
    }

    foreach (Face face, currentFaces) {
        float heightRatio = face.rect().height() / (float) mask->face().rect().height();
        float leftOffset = mask->face().rect().left() * heightRatio;
        float topOffset = mask->face().rect().top() * heightRatio;

        painter.drawImage(face.rect().left() - leftOffset, face.rect().top() - topOffset, mask->image().scaledToHeight(mask->image().height() * heightRatio));
    }




    this->replaceFrameTexture(currentImage);

    this->repaint();
}

void DisplayWidget::replaceFrameTexture(QImage& frame, int index)
{
    QImage glFrame = QGLWidget::convertToGLFormat(frame);

    glEnable(GL_TEXTURE_2D);

    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

    // Work with a specific texture
    glBindTexture(GL_TEXTURE_2D, frameTextures.at(index));
    GLfloat width, height;
    glGetTexLevelParameterfv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width);
    glGetTexLevelParameterfv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height);
    QSize lastGLFrameSize(width, height);
    if (lastGLFrameSize != glFrame.size()) {

        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
                     glFrame.width(), glFrame.height(),
                     0, GL_RGBA, GL_UNSIGNED_BYTE,
                     glFrame.bits());
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glOrtho(0, frame.width(), frame.height(), 0, 0, 1);
        glMatrixMode(GL_MODELVIEW);
    } else {
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,
                        glFrame.width(), glFrame.height(),
                        GL_RGBA, GL_UNSIGNED_BYTE,
                        glFrame.bits());
    }

    glDisable(GL_TEXTURE_2D);
}

void DisplayWidget::drawFrameTexture(int index)
{
    if (index > frameTextures.count() - 1)
        return;

    glMatrixMode(GL_MODELVIEW);

    // Homographies are applied only to textures
    glEnable(GL_TEXTURE_2D);

    // Work with a specific texture
    glBindTexture(GL_TEXTURE_2D, frameTextures.at(index));

    glBegin(GL_QUADS);
    {
        glTexCoord2i(0,1);
        glVertex2f(currentImage.width(), 0);

        glTexCoord2i(1,1);
        glVertex2f(0, 0);

        glTexCoord2i(1,0);
        glVertex2f(0, currentImage.height());

        glTexCoord2i(0,0);
        glVertex2f(currentImage.width(), currentImage.height());
    }
    glEnd();

    glDisable(GL_TEXTURE_2D);

}

void DisplayWidget::createNewFrameTexture()
{
    GLuint frameTexture;
    glGenTextures(1, &frameTexture);
    glBindTexture(GL_TEXTURE_2D, frameTexture);
    // when texture area is small, bilinear filter the closest mipmap
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    // when texture area is large, bilinear filter the original
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    // the texture wraps over at the edges (repeat)
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

    QImage glFrame = QGLWidget::convertToGLFormat(currentImage);

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
                 glFrame.width(), glFrame.height(),
                 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 glFrame.bits());

    frameTextures.append(frameTexture);

}

void DisplayWidget::initializeGL()
{
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glDisable(GL_BLEND);
    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glAlphaFunc(GL_GREATER, 0.7f);



}

void DisplayWidget::resizeGL(int w, int h)
{
    glViewport(0, 0, w, h);
}

void DisplayWidget::paintGL()
{
    glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
    glColor3f(0.0f, 0.0f, 0.0f);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    this->drawFrameTexture();
    //this->drawRects();
    //this->drawFaces();

}

void DisplayWidget::drawRects()
{

    foreach (QRect rect, currentRects) {
        this->drawRect(rect, Qt::green);
    }
}

void DisplayWidget::drawFaces()
{
    foreach (Face face, currentFaces) {
        this->drawRect(face.rect(), Qt::green);

        this->drawRect(face.leftEyeRect(), Qt::yellow);
        this->drawRect(face.rightEyeRect(), Qt::blue);

        this->drawRect(face.rect());


    }
}

void DisplayWidget::drawLine(QLine line, QColor color, float width)
{
    glLineWidth(width);
    glColor4f(color.redF(), color.greenF(), color.blueF(), color.alphaF());
    line = this->adjustForDisplay(line);
    glBegin(GL_LINES);
    {
        glVertex2i(line.x1(), line.y1());
        glVertex2i(line.x2(), line.y2());
    }
    glEnd();

}

void DisplayWidget::drawRect(QRect rect, QColor color, float width)
{
    glLineWidth(width);
    glColor4f(color.redF(), color.greenF(), color.blueF(), color.alphaF());
    rect = this->adjustForDisplay(rect);
    glBegin(GL_LINE_LOOP);
    {
        glVertex2i(rect.topLeft().x(), rect.topLeft().y());
        glVertex2i(rect.topRight().x(), rect.topLeft().y());
        glVertex2i(rect.bottomRight().x(), rect.bottomRight().y());
        glVertex2i(rect.bottomLeft().x(), rect.bottomLeft().y());
    }
    glEnd();
}

void DisplayWidget::drawEllipse(QPoint center, float rx, float ry, float angle, QColor color, float width)
{
    glLineWidth(width);
    glColor4f(color.redF(), color.greenF(), color.blueF(), color.alphaF());
    QPoint realCenter = this->adjustForDisplay(center);
    glBegin(GL_LINE_LOOP);
    {
        for (int i = 0; i < 360; i++) {
            int x = rx * cos(DEG2RAD(i));
            int y = ry * sin(DEG2RAD(i));

            glVertex2i(realCenter.x() + x, realCenter.y() + y);
        }
    }
    glEnd();
}

QRect DisplayWidget::adjustForDisplay(QRect rect)
{

    QRect newRect = rect;
    newRect.setRight(currentImage.width() - rect.left());
    newRect.setLeft(currentImage.width() - rect.right());

    return newRect;
}

QLine DisplayWidget::adjustForDisplay(QLine line)
{
    QLine newLine = line;
    newLine.setP1(this->adjustForDisplay(newLine.p1()));
    newLine.setP2(this->adjustForDisplay(newLine.p2()));
    return newLine;
}

QPoint DisplayWidget::adjustForDisplay(QPoint point)
{

    QPoint newPoint = point;
    newPoint.rx() = currentImage.width() - point.x();

    return newPoint;
}
